home *** CD-ROM | disk | FTP | other *** search
/ PC Advisor 2010 April / PCA177.iso / ESSENTIALS / Firefox Setup.exe / nonlocalized / chrome / browser.jar / content / browser / places / editBookmarkOverlay.js < prev    next >
Encoding:
Text File  |  2009-07-15  |  41.2 KB  |  1,133 lines

  1. /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
  2. /* ***** BEGIN LICENSE BLOCK *****
  3.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  4.  *
  5.  * The contents of this file are subject to the Mozilla Public License Version
  6.  * 1.1 (the "License"); you may not use this file except in compliance with
  7.  * the License. You may obtain a copy of the License at
  8.  * http://www.mozilla.org/MPL/
  9.  *
  10.  * Software distributed under the License is distributed on an "AS IS" basis,
  11.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  12.  * for the specific language governing rights and limitations under the
  13.  * License.
  14.  *
  15.  * The Original Code is the Places Bookmark Properties dialog.
  16.  *
  17.  * The Initial Developer of the Original Code is Google Inc.
  18.  * Portions created by the Initial Developer are Copyright (C) 2006
  19.  * the Initial Developer. All Rights Reserved.
  20.  *
  21.  * Contributor(s):
  22.  *   Asaf Romano <mano@mozilla.com>
  23.  *
  24.  * Alternatively, the contents of this file may be used under the terms of
  25.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  26.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  27.  * in which case the provisions of the GPL or the LGPL are applicable instead
  28.  * of those above. If you wish to allow use of your version of this file only
  29.  * under the terms of either the GPL or the LGPL, and not to allow others to
  30.  * use your version of this file under the terms of the MPL, indicate your
  31.  * decision by deleting the provisions above and replace them with the notice
  32.  * and other provisions required by the GPL or the LGPL. If you do not delete
  33.  * the provisions above, a recipient may use your version of this file under
  34.  * the terms of any one of the MPL, the GPL or the LGPL.
  35.  *
  36.  * ***** END LICENSE BLOCK ***** */
  37.  
  38. const LAST_USED_ANNO = "bookmarkPropertiesDialog/folderLastUsed";
  39. const STATIC_TITLE_ANNO = "bookmarks/staticTitle";
  40. const MAX_FOLDER_ITEM_IN_MENU_LIST = 5;
  41.  
  42. var gEditItemOverlay = {
  43.   _uri: null,
  44.   _itemId: -1,
  45.   _itemIds: [],
  46.   _uris: [],
  47.   _tags: [],
  48.   _allTags: [],
  49.   _multiEdit: false,
  50.   _itemType: -1,
  51.   _readOnly: false,
  52.   _microsummaries: null,
  53.   _hiddenRows: [],
  54.   _observersAdded: false,
  55.   _staticFoldersListBuilt: false,
  56.   _initialized: false,
  57.  
  58.   get itemId() {
  59.     return this._itemId;
  60.   },
  61.  
  62.   get multiEdit() {
  63.     return this._multiEdit;
  64.   },
  65.  
  66.   /**
  67.    * Determines the initial data for the item edited or added by this dialog
  68.    */
  69.   _determineInfo: function EIO__determineInfo(aInfo) {
  70.     // hidden rows
  71.     if (aInfo && aInfo.hiddenRows)
  72.       this._hiddenRows = aInfo.hiddenRows;
  73.     else
  74.       this._hiddenRows.splice(0);
  75.     // force-read-only
  76.     this._readOnly = aInfo && aInfo.forceReadOnly;
  77.   },
  78.  
  79.   _showHideRows: function EIO__showHideRows() {
  80.     var isBookmark = this._itemId != -1 &&
  81.                      this._itemType == Ci.nsINavBookmarksService.TYPE_BOOKMARK;
  82.     var isQuery = false;
  83.     if (this._uri)
  84.       isQuery = this._uri.schemeIs("place");
  85.  
  86.     this._element("nameRow").collapsed = this._hiddenRows.indexOf("name") != -1;
  87.     this._element("folderRow").collapsed =
  88.       this._hiddenRows.indexOf("folderPicker") != -1 || this._readOnly;
  89.     this._element("tagsRow").collapsed = !this._uri ||
  90.       this._hiddenRows.indexOf("tags") != -1 || isQuery;
  91.     // Collapse the tag selector if the item does not accept tags.
  92.     if (!this._element("tagsSelectorRow").collapsed &&
  93.         this._element("tagsRow").collapsed)
  94.       this.toggleTagsSelector();
  95.     this._element("descriptionRow").collapsed =
  96.       this._hiddenRows.indexOf("description") != -1 || this._readOnly;
  97.     this._element("keywordRow").collapsed = !isBookmark || this._readOnly ||
  98.       this._hiddenRows.indexOf("keyword") != -1 || isQuery;
  99.     this._element("locationRow").collapsed = !(this._uri && !isQuery) ||
  100.       this._hiddenRows.indexOf("location") != -1;
  101.     this._element("loadInSidebarCheckbox").collapsed = !isBookmark || isQuery ||
  102.       this._readOnly || this._hiddenRows.indexOf("loadInSidebar") != -1;
  103.     this._element("feedLocationRow").collapsed = !this._isLivemark ||
  104.       this._hiddenRows.indexOf("feedLocation") != -1;
  105.     this._element("siteLocationRow").collapsed = !this._isLivemark ||
  106.       this._hiddenRows.indexOf("siteLocation") != -1;
  107.     this._element("selectionCount").hidden = !this._multiEdit;
  108.   },
  109.  
  110.   /**
  111.    * Initialize the panel
  112.    * @param aFor
  113.    *        Either a places-itemId (of a bookmark, folder or a live bookmark),
  114.    *        an array of itemIds (used for bulk tagging), or a URI object (in 
  115.    *        which case, the panel would be initialized in read-only mode).
  116.    * @param [optional] aInfo
  117.    *        JS object which stores additional info for the panel
  118.    *        initialization. The following properties may bet set:
  119.    *        * hiddenRows (Strings array): list of rows to be hidden regardless
  120.    *          of the item edited. Possible values: "title", "location",
  121.    *          "description", "keyword", "loadInSidebar", "feedLocation",
  122.    *          "siteLocation", folderPicker"
  123.    *        * forceReadOnly - set this flag to initialize the panel to its
  124.    *          read-only (view) mode even if the given item is editable.
  125.    */
  126.   initPanel: function EIO_initPanel(aFor, aInfo) {
  127.     // For sanity ensure that the implementer has uninited the panel before
  128.     // trying to init it again, or we could end up leaking due to observers.
  129.     if (this._initialized)
  130.       this.uninitPanel(false);
  131.  
  132.     var aItemIdList;
  133.     if (aFor.length) {
  134.       aItemIdList = aFor;
  135.       aFor = aItemIdList[0];
  136.     }
  137.     else if (this._multiEdit) {
  138.       this._multiEdit = false;
  139.       this._tags = [];
  140.       this._uris = [];
  141.       this._allTags = [];
  142.       this._itemIds = [];
  143.       this._element("selectionCount").hidden = true;
  144.     }
  145.  
  146.     this._folderMenuList = this._element("folderMenuList");
  147.     this._folderTree = this._element("folderTree");
  148.  
  149.     this._determineInfo(aInfo);
  150.     if (aFor instanceof Ci.nsIURI) {
  151.       this._itemId = -1;
  152.       this._uri = aFor;
  153.       this._readOnly = true;
  154.     }
  155.     else {
  156.       this._itemId = aFor;
  157.       var container =  PlacesUtils.bookmarks.getFolderIdForItem(this._itemId);
  158.       this._itemType = PlacesUtils.bookmarks.getItemType(this._itemId);
  159.       if (this._itemType == Ci.nsINavBookmarksService.TYPE_BOOKMARK) {
  160.         this._uri = PlacesUtils.bookmarks.getBookmarkURI(this._itemId);
  161.         if (!this._readOnly) // If readOnly wasn't forced through aInfo
  162.           this._readOnly = PlacesUtils.livemarks.isLivemark(container);
  163.         this._initTextField("keywordField",
  164.                             PlacesUtils.bookmarks
  165.                                        .getKeywordForBookmark(this._itemId));
  166.         // Load In Sidebar checkbox
  167.         this._element("loadInSidebarCheckbox").checked =
  168.           PlacesUtils.annotations.itemHasAnnotation(this._itemId,
  169.                                                     LOAD_IN_SIDEBAR_ANNO);
  170.       }
  171.       else {
  172.         if (!this._readOnly) // If readOnly wasn't forced through aInfo
  173.           this._readOnly = false;
  174.  
  175.         this._uri = null;
  176.         this._isLivemark = PlacesUtils.livemarks.isLivemark(this._itemId);
  177.         if (this._isLivemark) {
  178.           var feedURI = PlacesUtils.livemarks.getFeedURI(this._itemId);
  179.           var siteURI = PlacesUtils.livemarks.getSiteURI(this._itemId);
  180.           this._initTextField("feedLocationField", feedURI.spec);
  181.           this._initTextField("siteLocationField", siteURI ? siteURI.spec : "");
  182.         }
  183.       }
  184.  
  185.       // folder picker
  186.       this._initFolderMenuList(container);
  187.  
  188.       // description field
  189.       this._initTextField("descriptionField", 
  190.                           PlacesUIUtils.getItemDescription(this._itemId));
  191.     }
  192.  
  193.     if (this._itemId == -1 ||
  194.         this._itemType == Ci.nsINavBookmarksService.TYPE_BOOKMARK) {
  195.       this._isLivemark = false;
  196.  
  197.       this._initTextField("locationField", this._uri.spec);
  198.       if (!aItemIdList) {
  199.         var tags = PlacesUtils.tagging.getTagsForURI(this._uri, {}).join(", ");
  200.         this._initTextField("tagsField", tags, false);
  201.       }
  202.       else {
  203.         this._multiEdit = true;
  204.         this._allTags = [];
  205.         this._itemIds = aItemIdList;
  206.         var nodeToCheck = 0;
  207.         for (var i = 0; i < aItemIdList.length; i++) {
  208.           if (aItemIdList[i] instanceof Ci.nsIURI) {
  209.             this._uris[i] = aItemIdList[i];
  210.             this._itemIds[i] = -1;
  211.           }
  212.           else
  213.             this._uris[i] = PlacesUtils.bookmarks.getBookmarkURI(this._itemIds[i], {});
  214.           this._tags[i] = PlacesUtils.tagging.getTagsForURI(this._uris[i], {});
  215.           if (this._tags[i].length < this._tags[nodeToCheck].length)
  216.             nodeToCheck =  i;
  217.         }
  218.         this._getCommonTags(nodeToCheck);
  219.         this._initTextField("tagsField", this._allTags.join(", "), false);
  220.         this._element("itemsCountText").value =
  221.           PlacesUIUtils.getFormattedString("detailsPane.multipleItems",
  222.                                            [this._itemIds.length]);
  223.       }
  224.  
  225.       // tags selector
  226.       this._rebuildTagsSelectorList();
  227.     }
  228.  
  229.     // name picker
  230.     this._initNamePicker();
  231.     
  232.     this._showHideRows();
  233.  
  234.     // observe changes
  235.     if (!this._observersAdded) {
  236.       if (this._itemId != -1)
  237.         PlacesUtils.bookmarks.addObserver(this, false);
  238.       window.addEventListener("unload", this, false);
  239.       this._observersAdded = true;
  240.     }
  241.  
  242.     this._initialized = true;
  243.   },
  244.  
  245.   _getCommonTags: function(aArrIndex) {
  246.     var tempArray = this._tags[aArrIndex];
  247.     var isAllTag;
  248.     for (var k = 0; k < tempArray.length; k++) {
  249.       isAllTag = true;
  250.       for (var j = 0; j < this._tags.length; j++) {
  251.         if (j == aArrIndex)
  252.           continue;
  253.         if (this._tags[j].indexOf(tempArray[k]) == -1) {
  254.           isAllTag = false;
  255.           break;
  256.         }
  257.       }
  258.       if (isAllTag)
  259.         this._allTags.push(tempArray[k]);
  260.     }
  261.   },
  262.  
  263.   _initTextField: function(aTextFieldId, aValue, aReadOnly) {
  264.     var field = this._element(aTextFieldId);
  265.     field.readOnly = aReadOnly !== undefined ? aReadOnly : this._readOnly;
  266.  
  267.     if (field.value != aValue) {
  268.       field.value = aValue;
  269.  
  270.       // clear the undo stack
  271.       var editor = field.editor;
  272.       if (editor)
  273.         editor.transactionManager.clear();
  274.     }
  275.   },
  276.  
  277.   /**
  278.    * Appends a menu-item representing a bookmarks folder to a menu-popup.
  279.    * @param aMenupopup
  280.    *        The popup to which the menu-item should be added.
  281.    * @param aFolderId
  282.    *        The identifier of the bookmarks folder.
  283.    * @return the new menu item.
  284.    */
  285.   _appendFolderItemToMenupopup:
  286.   function EIO__appendFolderItemToMenuList(aMenupopup, aFolderId) {
  287.     // First make sure the folders-separator is visible
  288.     this._element("foldersSeparator").hidden = false;
  289.  
  290.     var folderMenuItem = document.createElement("menuitem");
  291.     var folderTitle = PlacesUtils.bookmarks.getItemTitle(aFolderId)
  292.     folderMenuItem.folderId = aFolderId;
  293.     folderMenuItem.setAttribute("label", folderTitle);
  294.     folderMenuItem.className = "menuitem-iconic folder-icon";
  295.     aMenupopup.appendChild(folderMenuItem);
  296.     return folderMenuItem;
  297.   },
  298.  
  299.   _initFolderMenuList: function EIO__initFolderMenuList(aSelectedFolder) {
  300.     // clean up first
  301.     var menupopup = this._folderMenuList.menupopup;
  302.     while (menupopup.childNodes.length > 6)
  303.       menupopup.removeChild(menupopup.lastChild);
  304.  
  305.     const bms = PlacesUtils.bookmarks;
  306.     const annos = PlacesUtils.annotations;
  307.  
  308.     // Build the static list
  309.     var unfiledItem = this._element("unfiledRootItem");
  310.     if (!this._staticFoldersListBuilt) {
  311.       unfiledItem.label = bms.getItemTitle(PlacesUtils.unfiledBookmarksFolderId);
  312.       unfiledItem.folderId = PlacesUtils.unfiledBookmarksFolderId;
  313.       var bmMenuItem = this._element("bmRootItem");
  314.       bmMenuItem.label = bms.getItemTitle(PlacesUtils.bookmarksMenuFolderId);
  315.       bmMenuItem.folderId = PlacesUtils.bookmarksMenuFolderId;
  316.       var toolbarItem = this._element("toolbarFolderItem");
  317.       toolbarItem.label = bms.getItemTitle(PlacesUtils.toolbarFolderId);
  318.       toolbarItem.folderId = PlacesUtils.toolbarFolderId;
  319.       this._staticFoldersListBuilt = true;
  320.     }
  321.  
  322.     // List of recently used folders:
  323.     var folderIds = annos.getItemsWithAnnotation(LAST_USED_ANNO, { });
  324.  
  325.     /**
  326.      * The value of the LAST_USED_ANNO annotation is the time (in the form of
  327.      * Date.getTime) at which the folder has been last used.
  328.      *
  329.      * First we build the annotated folders array, each item has both the
  330.      * folder identifier and the time at which it was last-used by this dialog
  331.      * set. Then we sort it descendingly based on the time field.
  332.      */
  333.     this._recentFolders = [];
  334.     for (var i = 0; i < folderIds.length; i++) {
  335.       var lastUsed = annos.getItemAnnotation(folderIds[i], LAST_USED_ANNO);
  336.       this._recentFolders.push({ folderId: folderIds[i], lastUsed: lastUsed });
  337.     }
  338.     this._recentFolders.sort(function(a, b) {
  339.       if (b.lastUsed < a.lastUsed)
  340.         return -1;
  341.       if (b.lastUsed > a.lastUsed)
  342.         return 1;
  343.       return 0;
  344.     });
  345.  
  346.     var numberOfItems = Math.min(MAX_FOLDER_ITEM_IN_MENU_LIST,
  347.                                  this._recentFolders.length);
  348.     for (var i = 0; i < numberOfItems; i++) {
  349.       this._appendFolderItemToMenupopup(menupopup,
  350.                                         this._recentFolders[i].folderId);
  351.     }
  352.  
  353.     var defaultItem = this._getFolderMenuItem(aSelectedFolder);
  354.     this._folderMenuList.selectedItem = defaultItem;
  355.  
  356.     // Set a selectedIndex attribute to show special icons
  357.     this._folderMenuList.setAttribute("selectedIndex",
  358.                                       this._folderMenuList.selectedIndex);
  359.  
  360.     // Hide the folders-separator if no folder is annotated as recently-used
  361.     this._element("foldersSeparator").hidden = (menupopup.childNodes.length <= 6);
  362.     this._folderMenuList.disabled = this._readOnly;
  363.   },
  364.  
  365.   QueryInterface: function EIO_QueryInterface(aIID) {
  366.     if (aIID.equals(Ci.nsIMicrosummaryObserver) ||
  367.         aIID.equals(Ci.nsIDOMEventListener) ||
  368.         aIID.equals(Ci.nsINavBookmarkObserver) ||
  369.         aIID.equals(Ci.nsISupports))
  370.       return this;
  371.  
  372.     throw Cr.NS_ERROR_NO_INTERFACE;
  373.   },
  374.  
  375.   _element: function EIO__element(aID) {
  376.     return document.getElementById("editBMPanel_" + aID);
  377.   },
  378.  
  379.   _createMicrosummaryMenuItem:
  380.   function EIO__createMicrosummaryMenuItem(aMicrosummary) {
  381.     var menuItem = document.createElement("menuitem");
  382.  
  383.     // Store a reference to the microsummary in the menu item, so we know
  384.     // which microsummary this menu item represents when it's time to
  385.     // save changes or load its content.
  386.     menuItem.microsummary = aMicrosummary;
  387.  
  388.     // Content may have to be generated asynchronously; we don't necessarily
  389.     // have it now.  If we do, great; otherwise, fall back to the generator
  390.     // name, then the URI, and we trigger a microsummary content update. Once
  391.     // the update completes, the microsummary will notify our observer to
  392.     // update the corresponding menu-item.
  393.     // XXX Instead of just showing the generator name or (heaven forbid)
  394.     // its URI when we don't have content, we should tell the user that
  395.     // we're loading the microsummary, perhaps with some throbbing to let
  396.     // her know it is in progress.
  397.     if (aMicrosummary.content)
  398.       menuItem.setAttribute("label", aMicrosummary.content);
  399.     else {
  400.       menuItem.setAttribute("label", aMicrosummary.generator.name ||
  401.                                      aMicrosummary.generator.uri.spec);
  402.       aMicrosummary.update();
  403.     }
  404.  
  405.     return menuItem;
  406.   },
  407.  
  408.   _getItemStaticTitle: function EIO__getItemStaticTitle() {
  409.     if (this._itemId == -1)
  410.       return PlacesUtils.history.getPageTitle(this._uri);
  411.  
  412.     const annos = PlacesUtils.annotations;
  413.     if (annos.itemHasAnnotation(this._itemId, STATIC_TITLE_ANNO))
  414.       return annos.getItemAnnotation(this._itemId, STATIC_TITLE_ANNO);
  415.  
  416.     return PlacesUtils.bookmarks.getItemTitle(this._itemId);
  417.   },
  418.  
  419.   _initNamePicker: function EIO_initNamePicker() {
  420.     var userEnteredNameField = this._element("userEnteredName");
  421.     var namePicker = this._element("namePicker");
  422.     var droppable = false;
  423.  
  424.     userEnteredNameField.label = this._getItemStaticTitle();
  425.  
  426.     // clean up old entries
  427.     var menupopup = namePicker.menupopup;
  428.     while (menupopup.childNodes.length > 2)
  429.       menupopup.removeChild(menupopup.lastChild);
  430.  
  431.     if (this._microsummaries) {
  432.       this._microsummaries.removeObserver(this);
  433.       this._microsummaries = null;
  434.     }
  435.  
  436.     var itemToSelect = userEnteredNameField;
  437.     try {
  438.       if (this._itemId != -1 &&
  439.           this._itemType == Ci.nsINavBookmarksService.TYPE_BOOKMARK &&
  440.           !this._readOnly)
  441.         this._microsummaries = PlacesUIUtils.microsummaries
  442.                                             .getMicrosummaries(this._uri, -1);
  443.     }
  444.     catch(ex) {
  445.       // getMicrosummaries will throw an exception in at least two cases:
  446.       // 1. the bookmarked URI contains a scheme that the service won't
  447.       //    download for security reasons (currently it only handles http,
  448.       //    https, and file);
  449.       // 2. the page to which the URI refers isn't HTML or XML (the only two
  450.       //    content types the service knows how to summarize).
  451.       this._microsummaries = null;
  452.     }
  453.     if (this._microsummaries) {
  454.       var enumerator = this._microsummaries.Enumerate();
  455.  
  456.       if (enumerator.hasMoreElements()) {
  457.         // Show the drop marker if there are microsummaries
  458.         droppable = true;
  459.         while (enumerator.hasMoreElements()) {
  460.           var microsummary = enumerator.getNext()
  461.                                        .QueryInterface(Ci.nsIMicrosummary);
  462.           var menuItem = this._createMicrosummaryMenuItem(microsummary);
  463.           if (PlacesUIUtils.microsummaries
  464.                            .isMicrosummary(this._itemId, microsummary))
  465.             itemToSelect = menuItem;
  466.  
  467.           menupopup.appendChild(menuItem);
  468.         }
  469.       }
  470.  
  471.       this._microsummaries.addObserver(this);
  472.     }
  473.  
  474.     if (namePicker.selectedItem == itemToSelect)
  475.       namePicker.value = itemToSelect.label;
  476.     else
  477.       namePicker.selectedItem = itemToSelect;
  478.  
  479.     namePicker.setAttribute("droppable", droppable);
  480.     namePicker.readOnly = this._readOnly;
  481.  
  482.     // clear the undo stack
  483.     var editor = namePicker.editor;
  484.     if (editor)
  485.       editor.transactionManager.clear();
  486.   },
  487.  
  488.   // nsIMicrosummaryObserver
  489.   onContentLoaded: function EIO_onContentLoaded(aMicrosummary) {
  490.     var namePicker = this._element("namePicker");
  491.     var childNodes = namePicker.menupopup.childNodes;
  492.  
  493.     // 0: user-entered item; 1: separator
  494.     for (var i = 2; i < childNodes.length; i++) {
  495.       if (childNodes[i].microsummary == aMicrosummary) {
  496.         var newLabel = aMicrosummary.content;
  497.         // XXXmano: non-editable menulist would do this for us, see bug 360220
  498.         // We should fix editable-menulists to set the DOMAttrModified handler
  499.         // as well.
  500.         //
  501.         // Also note the order importance: if the label of the menu-item is
  502.         // set to something different than the menulist's current value,
  503.         // the menulist no longer has selectedItem set
  504.         if (namePicker.selectedItem == childNodes[i])
  505.           namePicker.value = newLabel;
  506.  
  507.         childNodes[i].label = newLabel;
  508.         return;
  509.       }
  510.     }
  511.   },
  512.  
  513.   onElementAppended: function EIO_onElementAppended(aMicrosummary) {
  514.     var namePicker = this._element("namePicker");
  515.     namePicker.menupopup
  516.               .appendChild(this._createMicrosummaryMenuItem(aMicrosummary));
  517.  
  518.     // Make sure the drop-marker is shown
  519.     namePicker.setAttribute("droppable", "true");
  520.   },
  521.  
  522.   uninitPanel: function EIO_uninitPanel(aHideCollapsibleElements) {
  523.     if (aHideCollapsibleElements) {
  524.       // hide the folder tree if it was previously visible
  525.       var folderTreeRow = this._element("folderTreeRow");
  526.       if (!folderTreeRow.collapsed)
  527.         this.toggleFolderTreeVisibility();
  528.  
  529.       // hide the tag selector if it was previously visible
  530.       var tagsSelectorRow = this._element("tagsSelectorRow");
  531.       if (!tagsSelectorRow.collapsed)
  532.         this.toggleTagsSelector();
  533.     }
  534.  
  535.     if (this._observersAdded) {
  536.       if (this._itemId != -1)
  537.         PlacesUtils.bookmarks.removeObserver(this);
  538.  
  539.       this._observersAdded = false;
  540.     }
  541.     if (this._microsummaries) {
  542.       this._microsummaries.removeObserver(this);
  543.       this._microsummaries = null;
  544.     }
  545.     this._itemId = -1;
  546.     this._uri = null;
  547.     this._uris = [];
  548.     this._tags = [];
  549.     this._allTags = [];
  550.     this._itemIds = [];
  551.     this._multiEdit = false;
  552.     this._initialized = false;
  553.   },
  554.  
  555.   onTagsFieldBlur: function EIO_onTagsFieldBlur() {
  556.     this._updateTags();
  557.   },
  558.  
  559.   _updateTags: function EIO__updateTags() {
  560.     if (this._multiEdit)
  561.       this._updateMultipleTagsForItems();
  562.     else
  563.       this._updateSingleTagForItem();
  564.   },
  565.  
  566.   _updateSingleTagForItem: function EIO__updateSingleTagForItem() {
  567.     var currentTags = PlacesUtils.tagging.getTagsForURI(this._uri, { });
  568.     var tags = this._getTagsArrayFromTagField();
  569.     if (tags.length > 0 || currentTags.length > 0) {
  570.       var tagsToRemove = [];
  571.       var tagsToAdd = [];
  572.       var txns = []; 
  573.       for (var i = 0; i < currentTags.length; i++) {
  574.         if (tags.indexOf(currentTags[i]) == -1)
  575.           tagsToRemove.push(currentTags[i]);
  576.       }
  577.       for (var i = 0; i < tags.length; i++) {
  578.         if (currentTags.indexOf(tags[i]) == -1)
  579.           tagsToAdd.push(tags[i]);
  580.       }
  581.  
  582.       if (tagsToRemove.length > 0)
  583.         txns.push(PlacesUIUtils.ptm.untagURI(this._uri, tagsToRemove));
  584.       if (tagsToAdd.length > 0)
  585.         txns.push(PlacesUIUtils.ptm.tagURI(this._uri, tagsToAdd));
  586.  
  587.       if (txns.length > 0) {
  588.         var aggregate = PlacesUIUtils.ptm.aggregateTransactions("Update tags",
  589.                                                                 txns);
  590.         PlacesUIUtils.ptm.doTransaction(aggregate);
  591.  
  592.         // Ensure the tagsField is in sync, clean it up from empty tags
  593.         var tags = PlacesUtils.tagging.getTagsForURI(this._uri, {}).join(", ");
  594.         this._initTextField("tagsField", tags, false);
  595.       }
  596.     }
  597.   },
  598.  
  599.   _updateMultipleTagsForItems: function EIO__updateMultipleTagsForItems() {
  600.     var tags = this._getTagsArrayFromTagField();
  601.     if (tags.length > 0 || this._allTags.length > 0) {
  602.       var tagsToRemove = [];
  603.       var tagsToAdd = [];
  604.       var txns = []; 
  605.       for (var i = 0; i < this._allTags.length; i++) {
  606.         if (tags.indexOf(this._allTags[i]) == -1)
  607.           tagsToRemove.push(this._allTags[i]);
  608.       }
  609.       for (var i = 0; i < this._tags.length; i++) {
  610.         tagsToAdd[i] = [];
  611.         for (var j = 0; j < tags.length; j++) {
  612.           if (this._tags[i].indexOf(tags[j]) == -1)
  613.             tagsToAdd[i].push(tags[j]);
  614.         }
  615.       }
  616.  
  617.       if (tagsToAdd.length > 0) {
  618.         for (i = 0; i < this._uris.length; i++) {
  619.           if (tagsToAdd[i].length > 0)
  620.             txns.push(PlacesUIUtils.ptm.tagURI(this._uris[i], tagsToAdd[i]));
  621.         }
  622.       }
  623.       if (tagsToRemove.length > 0) {
  624.         for (var i = 0; i < this._uris.length; i++)
  625.           txns.push(PlacesUIUtils.ptm.untagURI(this._uris[i], tagsToRemove));
  626.       }
  627.  
  628.       if (txns.length > 0) {
  629.         var aggregate = PlacesUIUtils.ptm.aggregateTransactions("Update tags",
  630.                                                                 txns);
  631.         PlacesUIUtils.ptm.doTransaction(aggregate);
  632.  
  633.         this._allTags = tags;
  634.         this._tags = [];
  635.         for (i = 0; i < this._uris.length; i++)
  636.           this._tags[i] = PlacesUtils.tagging.getTagsForURI(this._uris[i], {});
  637.  
  638.         // Ensure the tagsField is in sync, clean it up from empty tags
  639.         this._initTextField("tagsField", tags, false);
  640.       }
  641.     }
  642.   },
  643.  
  644.   onNamePickerInput: function EIO_onNamePickerInput() {
  645.     var title = this._element("namePicker").value;
  646.     this._element("userEnteredName").label = title;
  647.   },
  648.  
  649.   onNamePickerChange: function EIO_onNamePickerChange() {
  650.     if (this._itemId == -1)
  651.       return;
  652.  
  653.     var namePicker = this._element("namePicker")
  654.     var txns = [];
  655.     const ptm = PlacesUIUtils.ptm;
  656.  
  657.     // Here we update either the item title or its cached static title
  658.     var newTitle = this._element("userEnteredName").label;
  659.     if (this._getItemStaticTitle() != newTitle) {
  660.       if (PlacesUIUtils.microsummaries.hasMicrosummary(this._itemId)) {
  661.         // Note: this implicitly also takes care of the microsummary->static
  662.         // title case, the removeMicorosummary method in the service will set
  663.         // the item-title to the value of this annotation.
  664.         //
  665.         // XXXmano: use a transaction
  666.         PlacesUtils.setAnnotationsForItem(this._itemId,
  667.                                           [{name: STATIC_TITLE_ANNO,
  668.                                             value: newTitle}]);
  669.       }
  670.       else
  671.         txns.push(ptm.editItemTitle(this._itemId, newTitle));
  672.     }
  673.  
  674.     var newMicrosummary = namePicker.selectedItem.microsummary;
  675.  
  676.     // Only add a microsummary update to the transaction if the microsummary
  677.     // has actually changed, i.e. the user selected no microsummary, but the
  678.     // bookmark previously had one, or the user selected a microsummary which
  679.     // is not the one the bookmark previously had
  680.     if ((newMicrosummary == null &&
  681.          PlacesUIUtils.microsummaries.hasMicrosummary(this._itemId)) ||
  682.         (newMicrosummary != null &&
  683.          !PlacesUIUtils.microsummaries
  684.                        .isMicrosummary(this._itemId, newMicrosummary))) {
  685.       txns.push(ptm.editBookmarkMicrosummary(this._itemId, newMicrosummary));
  686.     }
  687.  
  688.     var aggregate = ptm.aggregateTransactions("Edit Item Title", txns);
  689.     ptm.doTransaction(aggregate);
  690.   },
  691.  
  692.   onDescriptionFieldBlur: function EIO_onDescriptionFieldInput() {
  693.     var description = this._element("descriptionField").value;
  694.     if (description != PlacesUIUtils.getItemDescription(this._itemId)) {
  695.       var txn = PlacesUIUtils.ptm
  696.                              .editItemDescription(this._itemId, description);
  697.       PlacesUIUtils.ptm.doTransaction(txn);
  698.     }
  699.   },
  700.  
  701.   onLocationFieldBlur: function EIO_onLocationFieldBlur() {
  702.     var uri;
  703.     try {
  704.       uri = PlacesUIUtils.createFixedURI(this._element("locationField").value);
  705.     }
  706.     catch(ex) { return; }
  707.  
  708.     if (!this._uri.equals(uri)) {
  709.       var txn = PlacesUIUtils.ptm.editBookmarkURI(this._itemId, uri);
  710.       PlacesUIUtils.ptm.doTransaction(txn);
  711.       this._uri = uri;
  712.     }
  713.   },
  714.  
  715.   onKeywordFieldBlur: function EIO_onKeywordFieldBlur() {
  716.     var keyword = this._element("keywordField").value;
  717.     if (keyword != PlacesUtils.bookmarks.getKeywordForBookmark(this._itemId)) {
  718.       var txn = PlacesUIUtils.ptm.editBookmarkKeyword(this._itemId, keyword);
  719.       PlacesUIUtils.ptm.doTransaction(txn);
  720.     }
  721.   },
  722.  
  723.   onFeedLocationFieldBlur: function EIO_onFeedLocationFieldBlur() {
  724.     var uri;
  725.     try {
  726.       uri = PlacesUIUtils.createFixedURI(this._element("feedLocationField").value);
  727.     }
  728.     catch(ex) { return; }
  729.  
  730.     var currentFeedURI = PlacesUtils.livemarks.getFeedURI(this._itemId);
  731.     if (!currentFeedURI.equals(uri)) {
  732.       var txn = PlacesUIUtils.ptm.editLivemarkFeedURI(this._itemId, uri);
  733.       PlacesUIUtils.ptm.doTransaction(txn);
  734.     }
  735.   },
  736.  
  737.   onSiteLocationFieldBlur: function EIO_onSiteLocationFieldBlur() {
  738.     var uri = null;
  739.     try {
  740.       uri = PlacesUIUtils.createFixedURI(this._element("siteLocationField").value);
  741.     }
  742.     catch(ex) {  }
  743.  
  744.     var currentSiteURI = PlacesUtils.livemarks.getSiteURI(this._itemId);
  745.     if (!uri || !currentSiteURI.equals(uri)) {
  746.       var txn = PlacesUIUtils.ptm.editLivemarkSiteURI(this._itemId, uri);
  747.       PlacesUIUtils.ptm.doTransaction(txn);
  748.     }
  749.   },
  750.  
  751.   onLoadInSidebarCheckboxCommand:
  752.   function EIO_onLoadInSidebarCheckboxCommand() {
  753.     var loadInSidebarChecked = this._element("loadInSidebarCheckbox").checked;
  754.     var txn = PlacesUIUtils.ptm.setLoadInSidebar(this._itemId,
  755.                                                  loadInSidebarChecked);
  756.     PlacesUIUtils.ptm.doTransaction(txn);
  757.   },
  758.  
  759.   toggleFolderTreeVisibility: function EIO_toggleFolderTreeVisibility() {
  760.     var expander = this._element("foldersExpander");
  761.     var folderTreeRow = this._element("folderTreeRow");
  762.     if (!folderTreeRow.collapsed) {
  763.       expander.className = "expander-down";
  764.       expander.setAttribute("tooltiptext",
  765.                             expander.getAttribute("tooltiptextdown"));
  766.       folderTreeRow.collapsed = true;
  767.       this._element("chooseFolderSeparator").hidden =
  768.         this._element("chooseFolderMenuItem").hidden = false;
  769.     }
  770.     else {
  771.       expander.className = "expander-up"
  772.       expander.setAttribute("tooltiptext",
  773.                             expander.getAttribute("tooltiptextup"));
  774.       folderTreeRow.collapsed = false;
  775.  
  776.       // XXXmano: Ideally we would only do this once, but for some odd reason,
  777.       // the editable mode set on this tree, together with its collapsed state
  778.       // breaks the view.
  779.       const FOLDER_TREE_PLACE_URI =
  780.         "place:excludeItems=1&excludeQueries=1&excludeReadOnlyFolders=1&folder=" +
  781.         PlacesUIUtils.allBookmarksFolderId;
  782.       this._folderTree.place = FOLDER_TREE_PLACE_URI;
  783.  
  784.       this._element("chooseFolderSeparator").hidden =
  785.         this._element("chooseFolderMenuItem").hidden = true;
  786.       var currentFolder = this._getFolderIdFromMenuList();
  787.       this._folderTree.selectItems([currentFolder]);
  788.       this._folderTree.focus();
  789.     }
  790.   },
  791.  
  792.   _getFolderIdFromMenuList:
  793.   function EIO__getFolderIdFromMenuList() {
  794.     var selectedItem = this._folderMenuList.selectedItem;
  795.     NS_ASSERT("folderId" in selectedItem,
  796.               "Invalid menuitem in the folders-menulist");
  797.     return selectedItem.folderId;
  798.   },
  799.  
  800.   /**
  801.    * Get the corresponding menu-item in the folder-menu-list for a bookmarks
  802.    * folder if such an item exists. Otherwise, this creates a menu-item for the
  803.    * folder. If the items-count limit (see MAX_FOLDERS_IN_MENU_LIST) is reached,
  804.    * the new item replaces the last menu-item.
  805.    * @param aFolderId
  806.    *        The identifier of the bookmarks folder.
  807.    */
  808.   _getFolderMenuItem:
  809.   function EIO__getFolderMenuItem(aFolderId) {
  810.     var menupopup = this._folderMenuList.menupopup;
  811.  
  812.     for (var i=0;  i < menupopup.childNodes.length; i++) {
  813.       if (menupopup.childNodes[i].folderId &&
  814.           menupopup.childNodes[i].folderId == aFolderId)
  815.         return menupopup.childNodes[i];
  816.     }
  817.  
  818.     // 3 special folders + separator + folder-items-count limit
  819.     if (menupopup.childNodes.length == 4 + MAX_FOLDER_ITEM_IN_MENU_LIST)
  820.       menupopup.removeChild(menupopup.lastChild);
  821.  
  822.     return this._appendFolderItemToMenupopup(menupopup, aFolderId);
  823.   },
  824.  
  825.   onFolderMenuListCommand: function EIO_onFolderMenuListCommand(aEvent) {
  826.     // Set a selectedIndex attribute to show special icons
  827.     this._folderMenuList.setAttribute("selectedIndex",
  828.                                       this._folderMenuList.selectedIndex);
  829.  
  830.     if (aEvent.target.id == "editBMPanel_chooseFolderMenuItem") {
  831.       // reset the selection back to where it was and expand the tree
  832.       // (this menu-item is hidden when the tree is already visible
  833.       var container = PlacesUtils.bookmarks.getFolderIdForItem(this._itemId);
  834.       var item = this._getFolderMenuItem(container);
  835.       this._folderMenuList.selectedItem = item;
  836.       // XXXmano HACK: setTimeout 100, otherwise focus goes back to the
  837.       // menulist right away
  838.       setTimeout(function(self) self.toggleFolderTreeVisibility(), 100, this);
  839.       return;
  840.     }
  841.  
  842.     // Move the item
  843.     var container = this._getFolderIdFromMenuList();
  844.     if (PlacesUtils.bookmarks.getFolderIdForItem(this._itemId) != container) {
  845.       var txn = PlacesUIUtils.ptm.moveItem(this._itemId, container, -1);
  846.       PlacesUIUtils.ptm.doTransaction(txn);
  847.  
  848.       // Mark the containing folder as recently-used if it isn't in the
  849.       // static list
  850.       if (container != PlacesUtils.unfiledBookmarksFolderId &&
  851.           container != PlacesUtils.toolbarFolderId &&
  852.           container != PlacesUtils.bookmarksMenuFolderId)
  853.         this._markFolderAsRecentlyUsed(container);
  854.     }
  855.  
  856.     // Update folder-tree selection
  857.     var folderTreeRow = this._element("folderTreeRow");
  858.     if (!folderTreeRow.collapsed) {
  859.       var selectedNode = this._folderTree.selectedNode;
  860.       if (!selectedNode ||
  861.           PlacesUtils.getConcreteItemId(selectedNode) != container)
  862.         this._folderTree.selectItems([container]);
  863.     }
  864.   },
  865.  
  866.   onFolderTreeSelect: function EIO_onFolderTreeSelect() {
  867.     var selectedNode = this._folderTree.selectedNode;
  868.  
  869.     // Disable the "New Folder" button if we cannot create a new folder
  870.     this._element("newFolderButton")
  871.         .disabled = !this._folderTree.insertionPoint || !selectedNode;
  872.  
  873.     if (!selectedNode)
  874.       return;
  875.  
  876.     var folderId = PlacesUtils.getConcreteItemId(selectedNode);
  877.     if (this._getFolderIdFromMenuList() == folderId)
  878.       return;
  879.  
  880.     var folderItem = this._getFolderMenuItem(folderId);
  881.     this._folderMenuList.selectedItem = folderItem;
  882.     folderItem.doCommand();
  883.   },
  884.  
  885.   _markFolderAsRecentlyUsed:
  886.   function EIO__markFolderAsRecentlyUsed(aFolderId) {
  887.     var txns = [];
  888.  
  889.     // Expire old unused recent folders
  890.     var anno = this._getLastUsedAnnotationObject(false);
  891.     while (this._recentFolders.length > MAX_FOLDER_ITEM_IN_MENU_LIST) {
  892.       var folderId = this._recentFolders.pop().folderId;
  893.       txns.push(PlacesUIUtils.ptm.setItemAnnotation(folderId, anno));
  894.     }
  895.  
  896.     // Mark folder as recently used
  897.     anno = this._getLastUsedAnnotationObject(true);
  898.     txns.push(PlacesUIUtils.ptm.setItemAnnotation(aFolderId, anno));
  899.  
  900.     var aggregate = PlacesUIUtils.ptm.aggregateTransactions("Update last used folders", txns);
  901.     PlacesUIUtils.ptm.doTransaction(aggregate);
  902.   },
  903.  
  904.   /**
  905.    * Returns an object which could then be used to set/unset the
  906.    * LAST_USED_ANNO annotation for a folder.
  907.    *
  908.    * @param aLastUsed
  909.    *        Whether to set or unset the LAST_USED_ANNO annotation.
  910.    * @returns an object representing the annotation which could then be used
  911.    *          with the transaction manager.
  912.    */
  913.   _getLastUsedAnnotationObject:
  914.   function EIO__getLastUsedAnnotationObject(aLastUsed) {
  915.     var anno = { name: LAST_USED_ANNO,
  916.                  type: Ci.nsIAnnotationService.TYPE_INT32,
  917.                  flags: 0,
  918.                  value: aLastUsed ? new Date().getTime() : null,
  919.                  expires: Ci.nsIAnnotationService.EXPIRE_NEVER };
  920.  
  921.     return anno;
  922.   },
  923.  
  924.   _rebuildTagsSelectorList: function EIO__rebuildTagsSelectorList() {
  925.     var tagsSelector = this._element("tagsSelector");
  926.     var tagsSelectorRow = this._element("tagsSelectorRow");
  927.     if (tagsSelectorRow.collapsed)
  928.       return;
  929.  
  930.     while (tagsSelector.hasChildNodes())
  931.       tagsSelector.removeChild(tagsSelector.lastChild);
  932.  
  933.     var tagsInField = this._getTagsArrayFromTagField();
  934.     var allTags = PlacesUtils.tagging.allTags;
  935.     for (var i = 0; i < allTags.length; i++) {
  936.       var tag = allTags[i];
  937.       var elt = document.createElement("listitem");
  938.       elt.setAttribute("type", "checkbox");
  939.       elt.setAttribute("label", tag);
  940.       if (tagsInField.indexOf(tag) != -1)
  941.         elt.setAttribute("checked", "true");
  942.  
  943.       tagsSelector.appendChild(elt);
  944.     }
  945.   },
  946.  
  947.   toggleTagsSelector: function EIO_toggleTagsSelector() {
  948.     var tagsSelector = this._element("tagsSelector");
  949.     var tagsSelectorRow = this._element("tagsSelectorRow");
  950.     var expander = this._element("tagsSelectorExpander");
  951.     if (tagsSelectorRow.collapsed) {
  952.       expander.className = "expander-up";
  953.       expander.setAttribute("tooltiptext",
  954.                             expander.getAttribute("tooltiptextup"));
  955.       tagsSelectorRow.collapsed = false;
  956.       this._rebuildTagsSelectorList();
  957.  
  958.       // This is a no-op if we've added the listener.
  959.       tagsSelector.addEventListener("CheckboxStateChange", this, false);
  960.     }
  961.     else {
  962.       expander.className = "expander-down";
  963.       expander.setAttribute("tooltiptext",
  964.                             expander.getAttribute("tooltiptextdown"));
  965.       tagsSelectorRow.collapsed = true;
  966.     }
  967.   },
  968.  
  969.   _getTagsArrayFromTagField: function EIO__getTagsArrayFromTagField() {
  970.     // we don't require the leading space (after each comma)
  971.     var tags = this._element("tagsField").value.split(",");
  972.     for (var i=0; i < tags.length; i++) {
  973.       // remove trailing and leading spaces
  974.       tags[i] = tags[i].replace(/^\s+/, "").replace(/\s+$/, "");
  975.  
  976.       // remove empty entries from the array.
  977.       if (tags[i] == "") {
  978.         tags.splice(i, 1);
  979.         i--;
  980.       }
  981.     }
  982.     return tags;
  983.   },
  984.  
  985.   newFolder: function EIO_newFolder() {
  986.     var ip = this._folderTree.insertionPoint;
  987.  
  988.     // default to the bookmarks menu folder
  989.     if (!ip || ip.itemId == PlacesUIUtils.allBookmarksFolderId) {
  990.         ip = new InsertionPoint(PlacesUtils.bookmarksMenuFolderId,
  991.                                 PlacesUtils.bookmarks.DEFAULT_INDEX,
  992.                                 Ci.nsITreeView.DROP_ON);
  993.     }
  994.  
  995.     // XXXmano: add a separate "New Folder" string at some point...
  996.     var defaultLabel = this._element("newFolderButton").label;
  997.     var txn = PlacesUIUtils.ptm.createFolder(defaultLabel, ip.itemId, ip.index);
  998.     PlacesUIUtils.ptm.doTransaction(txn);
  999.     this._folderTree.focus();
  1000.     this._folderTree.selectItems([this._lastNewItem]);
  1001.     this._folderTree.startEditing(this._folderTree.view.selection.currentIndex,
  1002.                                   this._folderTree.columns.getFirstColumn());
  1003.   },
  1004.  
  1005.   // nsIDOMEventListener
  1006.   handleEvent: function EIO_nsIDOMEventListener(aEvent) {
  1007.     switch (aEvent.type) {
  1008.     case "CheckboxStateChange":
  1009.       // Update the tags field when items are checked/unchecked in the listbox
  1010.       var tags = this._getTagsArrayFromTagField();
  1011.  
  1012.       if (aEvent.target.checked) {
  1013.         if (tags.indexOf(aEvent.target.label) == -1)
  1014.           tags.push(aEvent.target.label);
  1015.       }
  1016.       else {
  1017.         var indexOfItem = tags.indexOf(aEvent.target.label);
  1018.         if (indexOfItem != -1)
  1019.           tags.splice(indexOfItem, 1);
  1020.       }
  1021.       this._element("tagsField").value = tags.join(", ");
  1022.       this._updateTags();
  1023.       break;
  1024.     case "unload":
  1025.       this.uninitPanel(false);
  1026.       break;
  1027.     }
  1028.   },
  1029.  
  1030.   // nsINavBookmarkObserver
  1031.   onItemChanged: function EIO_onItemChanged(aItemId, aProperty,
  1032.                                             aIsAnnotationProperty, aValue) {
  1033.     if (this._itemId != aItemId) {
  1034.       if (aProperty == "title") {
  1035.         // If the title of a folder which is listed within the folders
  1036.         // menulist has been changed, we need to update the label of its
  1037.         // representing element.
  1038.         var menupopup = this._folderMenuList.menupopup;
  1039.         for (var i=0; i < menupopup.childNodes.length; i++) {
  1040.           if (menupopup.childNodes[i].folderId == aItemId) {
  1041.             menupopup.childNodes[i].label = aValue;
  1042.             break;
  1043.           }
  1044.         }
  1045.       }
  1046.  
  1047.       return;
  1048.     }
  1049.  
  1050.     switch (aProperty) {
  1051.     case "title":
  1052.       if (PlacesUtils.annotations.itemHasAnnotation(this._itemId,
  1053.                                                     STATIC_TITLE_ANNO))
  1054.         return;  // onContentLoaded updates microsummary-items
  1055.  
  1056.       var userEnteredNameField = this._element("userEnteredName");
  1057.       if (userEnteredNameField.value != aValue) {
  1058.         userEnteredNameField.value = aValue;
  1059.         var namePicker = this._element("namePicker");
  1060.         if (namePicker.selectedItem == userEnteredNameField) {
  1061.           namePicker.label = aValue;
  1062.  
  1063.           // clear undo stack
  1064.           namePicker.editor.transactionManager.clear();
  1065.         }
  1066.       }
  1067.       break;
  1068.     case "uri":
  1069.       var locationField = this._element("locationField");
  1070.       if (locationField.value != aValue) {
  1071.         this._uri = Cc["@mozilla.org/network/io-service;1"].
  1072.                     getService(Ci.nsIIOService).
  1073.                     newURI(aValue, null, null);
  1074.         this._initTextField("locationField", this._uri.spec);
  1075.         this._initNamePicker(); // for microsummaries
  1076.         this._initTextField("tagsField",
  1077.                              PlacesUtils.tagging
  1078.                                         .getTagsForURI(this._uri, { }).join(", "),
  1079.                             false);
  1080.         this._rebuildTagsSelectorList();
  1081.       }
  1082.       break;
  1083.     case "keyword":
  1084.       this._initTextField("keywordField",
  1085.                           PlacesUtils.bookmarks
  1086.                                      .getKeywordForBookmark(this._itemId));
  1087.       break;
  1088.     case DESCRIPTION_ANNO:
  1089.       this._initTextField("descriptionField",
  1090.                           PlacesUIUtils.getItemDescription(this._itemId));
  1091.       break;
  1092.     case LOAD_IN_SIDEBAR_ANNO:
  1093.       this._element("loadInSidebarCheckbox").checked =
  1094.         PlacesUtils.annotations.itemHasAnnotation(this._itemId,
  1095.                                                   LOAD_IN_SIDEBAR_ANNO);
  1096.       break;
  1097.     case LMANNO_FEEDURI:
  1098.       var feedURISpec = PlacesUtils.livemarks.getFeedURI(this._itemId).spec;
  1099.       this._initTextField("feedLocationField", feedURISpec);
  1100.       break;
  1101.     case LMANNO_SITEURI:
  1102.       var siteURISpec = "";
  1103.       var siteURI = PlacesUtils.livemarks.getSiteURI(this._itemId);
  1104.       if (siteURI)
  1105.         siteURISpec = siteURI.spec;
  1106.       this._initTextField("siteLocationField", siteURISpec);
  1107.       break;
  1108.     }
  1109.   },
  1110.  
  1111.   onItemMoved: function EIO_onItemMoved(aItemId, aOldParent, aOldIndex,
  1112.                                         aNewParent, aNewIndex) {
  1113.     if (aItemId != this._itemId ||
  1114.         aNewParent == this._getFolderIdFromMenuList())
  1115.       return;
  1116.  
  1117.     var folderItem = this._getFolderMenuItem(aNewParent);
  1118.  
  1119.     // just setting selectItem _does not_ trigger oncommand, so we don't
  1120.     // recurse
  1121.     this._folderMenuList.selectedItem = folderItem;
  1122.   },
  1123.  
  1124.   onItemAdded: function EIO_onItemAdded(aItemId, aFolder, aIndex) {
  1125.     this._lastNewItem = aItemId;
  1126.   },
  1127.  
  1128.   onBeginUpdateBatch: function() { },
  1129.   onEndUpdateBatch: function() { },
  1130.   onItemRemoved: function() { },
  1131.   onItemVisited: function() { },
  1132. };
  1133.